1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
import { PostComments, Share } from '@/app/(home)/posts/[slug]/page.client';
import { PostJsonLd } from '@/components/json-ld';
import { Section } from '@/components/section';
import { TagCard } from '@/components/tags/tag-card';
import { createMetadata } from '@/lib/metadata';
import { metadataImage } from '@/lib/metadata-image';
import { type Page as MDXPage, getPost, getPosts } from '@/lib/source';
import { cn } from '@/lib/utils';
import { File, Files, Folder } from 'fumadocs-ui/components/files';
import { InlineTOC } from 'fumadocs-ui/components/inline-toc';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import Balancer from 'react-wrap-balancer';
import { description as homeDescription } from 'src/app/layout.config';
function Header(props: { page: MDXPage; tags?: string[] }) {
const { page, tags } = props;
return (
<Section className='p-4 lg:p-6'>
<div
className={cn(
'flex flex-col items-start justify-center gap-4 py-8 md:gap-6',
'sm:items-center sm:rounded-lg sm:border sm:bg-muted/70 sm:px-8 sm:py-20 sm:shadow-xs sm:dark:bg-muted',
)}
>
<div className='flex flex-col gap-2 sm:text-center md:gap-4'>
<h1 className='max-w-4xl font-bold text-3xl leading-tight tracking-tight sm:text-4xl sm:leading-tight md:text-5xl md:leading-tight'>
<Balancer>{page.data.title}</Balancer>
</h1>
<p className='mx-auto max-w-4xl'>
<Balancer>{page.data.description}</Balancer>
</p>
</div>
<div className='flex flex-wrap gap-2'>
{tags?.map((tag) => (
<TagCard name={tag} key={tag} className=' border border-border ' />
))}
</div>
</div>
</Section>
);
}
export default async function Page(props: {
params: Promise<{ slug: string }>;
}) {
const params = await props.params;
const page = getPost([params.slug]);
if (!page) notFound();
const { body: Mdx, toc, tags, lastModified } = page.data;
const lastUpdate = lastModified ? new Date(lastModified) : undefined;
return (
<>
<Header page={page} tags={tags} />
<Section className='h-full' sectionClassName='flex flex-1'>
<article className='flex min-h-full flex-col lg:flex-row'>
<div className='flex flex-1 flex-col gap-4'>
<InlineTOC
items={toc}
className='rounded-none border-0 border-border/70 border-b border-dashed dark:border-border'
/>
<div className='prose min-w-0 flex-1 px-4'>
<Mdx
components={{
...defaultMdxComponents,
File,
Files,
Folder,
Tabs,
Tab,
}}
/>
</div>
<PostComments
slug={params.slug}
className='[&_form>div]:!rounded-none rounded-none border-0 border-border/70 border-t border-dashed dark:border-border'
/>
</div>
<div className='flex flex-col gap-4 p-4 text-sm lg:sticky lg:top-[4rem] lg:h-[calc(100vh-4rem)] lg:w-[250px] lg:self-start lg:overflow-y-auto lg:border-border/70 lg:border-l lg:border-dashed lg:dark:border-border'>
<div>
<p className='mb-1 text-fd-muted-foreground'>Written by</p>
<p className='font-medium'>{page.data.author}</p>
</div>
<div>
<p className='mb-1 text-fd-muted-foreground text-sm'>
Created At
</p>
<p className='font-medium'>
{new Date(page.data.date ?? page.file.name).toDateString()}
</p>
</div>
{lastUpdate && (
<div>
<p className='mb-1 text-fd-muted-foreground text-sm'>
Updated At
</p>
<p className='font-medium'>{lastUpdate.toDateString()}</p>
</div>
)}
<Share url={page.url} />
</div>
</article>
</Section>
<PostJsonLd page={page} />
</>
);
}
export async function generateMetadata(props: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const params = await props.params;
const page = getPost([params.slug]);
if (!page) notFound();
const title = page.data.title;
const description = page.data.description ?? homeDescription;
return createMetadata(
metadataImage.withImage(page.slugs, {
title,
description,
openGraph: {
url: `/posts/${page.slugs.join('/')}`,
},
alternates: {
canonical: page.url,
},
}),
);
}
export function generateStaticParams(): { slug: string | undefined }[] {
return getPosts().map((page) => ({
slug: page.slugs[0],
}));
}
|